package com.xy.biz.mc.connection.websocket;

import com.xy.tool.util.LogUtils;
import lombok.extern.slf4j.Slf4j;
import com.xy.biz.mc.service.ConnectionService;
import com.xy.biz.mc.connection.SocketConnection;
import com.xy.lang.domain.Result;
import com.xy.lang.util.UuidUtils;
import com.xy.tool.context.UserContext;
import com.xy.tool.jwt.TokenParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Objects;

@Slf4j
@ServerEndpoint("/websocket/{token}")
@Component
public class WebSocketConnection implements SocketConnection {

    private String id; //标识，用于区分socket连接
    private Long userId; //用户Id
    private Long userGroupId; //用户分组Id

    private Session session; //web socket的会话对象

    /**
    * 最后一次接收到心跳的时间
    */
    private Long lastHeartbeat;

    /**
     * 心跳-请求
     */
    private static final String HEART_BEAT_PING = "ping";
    /**
     * 心跳-响应
     */
    private static final String HEART_BEAT_PONG = "pong";

    /**
     * 超时时限,5分钟
     */
    private static final int HEALTH_TIMEOUT = 5 * 60 * 1000;

    private static ConnectionService connectionService; //连接服务对象
    public static void setConnectionService(ConnectionService connectionService) {
        WebSocketConnection.connectionService = connectionService;
    }

    private static TokenParser tokenParser; // token处理
    public static void setTokenParser(TokenParser tokenParser) {
        WebSocketConnection.tokenParser = tokenParser;
    }

    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        this.id = UuidUtils.compactSystemUuid();
        LogUtils.info(log, "----------web socket open: (" + id + "), token: " + token + ", " + Thread.currentThread().getName() + "----------");

        this.session = session;
        if(!StringUtils.hasText(token)) {
            manualClose("请输入token");
            return;
        }
        Result<UserContext> result = tokenParser.verify(token);
        if(result.wasFailure()) {
            manualClose("无效的token");
            return;
        }
        UserContext userContext = result.getData();
        this.userId = userContext.getUserId();
        this.userGroupId = userContext.getUserGroupId();
        this.lastHeartbeat = System.currentTimeMillis();
        LogUtils.info(log, "The current user is: " + userId);

        connectionService.addConnection(this.userId, this.userGroupId, this);
    }

    @OnClose
    public void onClose(CloseReason closeReason) {
        LogUtils.info(log, "----------web socket close: (" + id + ") " + Thread.currentThread().getName() + "----------");
        LogUtils.info(log, "The current user is: " + userId);
        LogUtils.info(log, "" + closeReason);

        connectionService.removeConnection(this.userId, this.userGroupId, this);
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        LogUtils.info(log, "receive message is: " + message);
        if(HEART_BEAT_PING.equals(message)){
            this.lastHeartbeat = System.currentTimeMillis();
            sendText(HEART_BEAT_PONG);
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("web socket error: ", error);
    }

    @Override
    public void sendText(String text) {
        try {
            session.getBasicRemote().sendText(text);
        } catch (IOException e) {
            log.error("send message error", e);
        }
    }

    @Override
    public void manualClose(String reason) {
        try {
            session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, reason == null ? "主动关闭" : reason));
        } catch (IOException e) {
            log.error("close error", e);
        }
    }

    @Override
    public boolean isTimeOut() {
        Long currentTimeStamp = System.currentTimeMillis();
        return currentTimeStamp > lastHeartbeat + HEALTH_TIMEOUT;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        WebSocketConnection that = (WebSocketConnection) o;
        return Objects.equals(id, that.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

}
